//===============================================================================
// XGenPlus Tool
// ==============================================================================
// Please visit http://www.codeplex.com/xgenplus for latest updates.
//
// This source is subject to the GNU General Public License v2.
// 
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using System.Text;
using System.Collections.Generic;

namespace XGenPlus.Generator
{
    internal sealed class XmlSerializerGenerator
    {
        Logger xgenLog = null;

        public XmlSerializerGenerator(Logger l)
        {
            xgenLog = l;
        }

        #region Regular Expressions


        /// <summary>
        /// Matches an assembly level AssemblyVersionAttribute attribute declaration.
        /// </summary>
        Regex AssemblyVersion = new Regex(@"\[assembly:System.Reflection.AssemblyVersionAttribute[^\]]*\]",
            RegexOptions.Compiled | RegexOptions.Multiline);

        #endregion

        #region Code templates


        const string serializerFactoryBeginTemplate = @"

        using System;
        using System.Collections;
        using System.Collections.Generic;
        using System.CodeDom;
        using System.CodeDom.Compiler;
        using System.IO;
        using System.Reflection;
        using System.Text.RegularExpressions;
        using System.Xml.Serialization;
        using System.Text;
    
        namespace $factoryNameSpace$ {
    

        /// <summary>
        /// Serializer factory
        /// </summary>
        public class SerializerFactory : XGenPlus.SerializerLib.ISerializerFactory
        {


            /// <summary>
            /// Returns the XML Serializer for a specific type
            /// </summary>
            public XmlSerializer GetSerializer(Type t)
            {
                string typeName=t.Namespace + '.' + t.Name;

                if (t==null)
                    return null;
        ";


        const string serializerFactoryEndTemplate = @"
                
               else
                 return null; 

            }
         }
        }";


        const string serializerFactoryElseIfTemplate = @"
              else if (t==typeof($nameOfType$)) return new $serializerOfType$();";




        #endregion Code templates

        #region Methods


        public void DoTypedSerialization()
        {

        }


        /// <summary>
        /// Main function which returns the generated code for the specific types
        /// </summary>
        /// <param name="types"></param>
        /// <param name="targetPath"></param>
        /// <param name="excludeNamespaces"></param>
        /// <param name="factoryNameSpace"></param>
        /// <param name="allType"></param>
        /// <returns></returns>
        public string GenerateCode(Type[] types, string targetPath, List<string> excludeNamespaces, List<string> includeNamespaces, string factoryNameSpace, bool serializeAll)
        {
            string output = String.Empty;
            string fromNameSpace = String.Empty;

            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

            if (excludeNamespaces == null) excludeNamespaces = new List<string>();
            if (includeNamespaces == null) includeNamespaces = new List<string>();


            using (StreamWriter logWriter = new StreamWriter(Path.Combine(targetPath, "..\\Lastrun_Errors.log")))
            {

                using (StreamWriter factoryWriter = new StreamWriter(Path.Combine(targetPath, "Factory.cs")))
                {

                    factoryWriter.WriteLine(serializerFactoryBeginTemplate.Replace("$factoryNameSpace$", factoryNameSpace));

                    foreach (Type type in types)
                    {

                        string nameSpace = string.Empty;

                        if (type.Namespace != null)
                            nameSpace = type.Namespace + ".Serializers" + '.' + type.Name;
                        else
                            nameSpace = factoryNameSpace + ".Serializers" + '.' + type.Name;


                        bool exclude = FindTypeInList(excludeNamespaces, type);
                        bool include = FindTypeInList(includeNamespaces, type);
                        bool skip = false;

                        if (excludeNamespaces.Count == 0 && includeNamespaces.Count != 0)
                            exclude = true;
                        if (excludeNamespaces.Count > 0 && includeNamespaces.Count == 0)
                            include = false;

                        //Check if this type has serializable attribute defined
                        if (!serializeAll)
                            skip = IfSkip(type);

                        //Skip if this type is a nested type
                        if (type.IsNested) skip = true;


                        if ((!exclude || include) && !skip)
                        {

                            try
                            {
                                sw.Reset();
                                sw.Start();

                                xgenLog.Write("starting-" + type.ToString());

                                output = GenerateCodeForType(type, nameSpace) + Environment.NewLine;
                                string createString = string.Empty;
                                createString = serializerFactoryElseIfTemplate.Replace("$nameOfType$", type.ToString());

                                createString = createString.Replace("$serializerOfType$", nameSpace + "." + type.Name + "Serializer");


                                factoryWriter.WriteLine(createString);
                                sw.Stop();

                                using (StreamWriter codeWriter = new StreamWriter(Path.Combine(targetPath, nameSpace + ".cs")))
                                {
                                    codeWriter.Write(output);
                                    xgenLog.Write("success -" + type.ToString() + " - " + sw.ElapsedMilliseconds + " msecs");
                                }
                            }

                            catch (Exception e)
                            {
                                xgenLog.Write("failure -" + type.ToString());
                                logWriter.Write("Generate Serializer failed for type - " + type.ToString(), logWriter);
                                logWriter.Write(e.ToString(), logWriter);
                                logWriter.Write("\n--------------------------------------\n");
                            }
                        }

                        else
                        {
                            //logwriter.Write("skipped -" + type.ToString());
                        }
                    }

                    factoryWriter.WriteLine(serializerFactoryEndTemplate);


                }
            }

            return output;
        }

        /// <summary>
        /// Check whether the type is in the given list
        /// </summary>
        /// <param name="namespaceList"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        private bool FindTypeInList(List<string> namespaceList, Type type)
        {
            bool inlist = false;

            if (namespaceList == null || namespaceList.Count == 0) return inlist;

            namespaceList.ForEach(delegate(string item)
            {

                if (type.ToString() != null && type.ToString().ToLower().StartsWith(item.ToLower()))
                {
                    inlist = true;
                    return;
                }
            });
            return inlist;
        }

        /// <summary>
        /// Generate the code for a specific type
        /// </summary>
        /// <param name="type"></param>
        /// <param name="targetNamespace"></param>
        /// <returns></returns>
        private string GenerateCodeForType(Type type, string targetNamespace)
        {
            string output = GetXmlSerializerOutput(type);
            output = CleanupNamespace(targetNamespace, output);
            return output;

        }


        private bool IfSkip(Type t)
        {

            foreach (Attribute at in t.GetCustomAttributes(true))
            {
                System.SerializableAttribute sattr = null;
                sattr = at as System.SerializableAttribute;
                if (null != sattr)
                {
                    //Don't skip
                    return false;
                }
            }
            //Skip
            return true;
        }



        private string CleanupNamespace(string targetNamespace, string output)
        {
            // Remove assembly version attribute.
            output = AssemblyVersion.Replace(output, "");
            // Replace namespace.
            output = output.Replace("Microsoft.Xml.Serialization.GeneratedAssembly", targetNamespace);
            return output;
        }

        /// <summary>
        /// Returns the X
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private string GetXmlSerializerOutput(Type type)
        {
            string output;

            XmlSerializer ser = new XmlSerializer(type);

            FieldInfo fitempasm = ser.GetType().GetField("tempAssembly", BindingFlags.NonPublic | BindingFlags.Instance);
            object tempasm = fitempasm.GetValue(ser);
            FieldInfo fiasm = tempasm.GetType().GetField("assembly", BindingFlags.NonPublic | BindingFlags.Instance);
            Assembly asm = (Assembly)fiasm.GetValue(tempasm);

            string codebase = new Uri(asm.CodeBase).LocalPath;
            string code = Path.Combine(
                Path.GetDirectoryName(codebase),
                Path.GetFileNameWithoutExtension(codebase) + ".0.cs");

            string original;

            using (StreamReader sr = new StreamReader(code))
            {
                output = sr.ReadToEnd();
                original = output;
            }

            ser = null;
            fitempasm = null;
            tempasm = null;
            fiasm = null;
            asm = null;

            //Clear out all the temporary files
            try
            {
                string[] files = Directory.GetFiles(Path.GetDirectoryName(codebase), Path.GetFileNameWithoutExtension(codebase) + "*.*");
                for (int i = 0; i < files.Length; i++)
                    try
                    {
                        //Delete all files which are not loaded in the application domain
                        if (!files[i].EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) &&
                            !files[i].EndsWith(".pdb", StringComparison.InvariantCultureIgnoreCase))
                            File.Delete(files[i]);
                    }
                    catch { }

            }
            catch { }

            return output;
        }

        #endregion
    }
}
